﻿namespace Pub.Class
{
    using System;
    using System.Data;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Runtime.CompilerServices;

    public class DataTableEntityBuilder<Entity>
    {
        private static readonly MethodInfo getValueMethod;
        private Load<Entity> handler;
        private static readonly MethodInfo isDBNullMethod;

        static DataTableEntityBuilder()
        {
            DataTableEntityBuilder<Entity>.getValueMethod = typeof(DataRow).GetMethod("get_Item", new Type[] { typeof(int) });
            DataTableEntityBuilder<Entity>.isDBNullMethod = typeof(DataRow).GetMethod("IsNull", new Type[] { typeof(int) });
        }

        private DataTableEntityBuilder()
        {
        }

        public Entity Build(DataRow dataRecord)
        {
            return this.handler(dataRecord);
        }

        public static DataTableEntityBuilder<Entity> CreateBuilder(DataRow dataRecord)
        {
            DataTableEntityBuilder<Entity> builder = new DataTableEntityBuilder<Entity>();
            DynamicMethod method = new DynamicMethod("DataTableDynamicCreateEntity", typeof(Entity), new Type[] { typeof(DataRow) }, typeof(Entity), true);
            ILGenerator iLGenerator = method.GetILGenerator();
            LocalBuilder local = iLGenerator.DeclareLocal(typeof(Entity));
            iLGenerator.Emit(OpCodes.Newobj, typeof(Entity).GetConstructor(Type.EmptyTypes));
            iLGenerator.Emit(OpCodes.Stloc, local);
            for (int i = 0; i < dataRecord.ItemArray.Length; i++)
            {
                PropertyInfo property = typeof(Entity).GetProperty(dataRecord.Table.Columns[i].ColumnName);
                Label label = iLGenerator.DefineLabel();
                if (property.IsNotNull() && property.GetSetMethod().IsNotNull())
                {
                    iLGenerator.Emit(OpCodes.Ldarg_0);
                    iLGenerator.Emit(OpCodes.Ldc_I4, i);
                    iLGenerator.Emit(OpCodes.Callvirt, DataTableEntityBuilder<Entity>.isDBNullMethod);
                    iLGenerator.Emit(OpCodes.Brtrue, label);
                    iLGenerator.Emit(OpCodes.Ldloc, local);
                    iLGenerator.Emit(OpCodes.Ldarg_0);
                    iLGenerator.Emit(OpCodes.Ldc_I4, i);
                    iLGenerator.Emit(OpCodes.Callvirt, DataTableEntityBuilder<Entity>.getValueMethod);
                    iLGenerator.Emit(OpCodes.Unbox_Any, property.PropertyType);
                    iLGenerator.Emit(OpCodes.Callvirt, property.GetSetMethod());
                    iLGenerator.MarkLabel(label);
                }
            }
            iLGenerator.Emit(OpCodes.Ldloc, local);
            iLGenerator.Emit(OpCodes.Ret);
            builder.handler = (Load<Entity>) method.CreateDelegate(typeof(Load<Entity>));
            return builder;
        }

        private delegate Entity Load<T>(DataRow dataRecord);
    }
}

